package gestordeentidades.identitymap;

import gestordeentidades.identitymap.interfaces.IEntityMap;
import gestordeentidades.identitymap.interfaces.IUnityOfWork;
import gestordeentidades.idgenerators.IIDgenerator;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import gestordeentidades.persistance.DataBaseObject;
import gestordeentidades.persistance.interfaces.IPersistance;

/**
 * Trabalho DAS - 2014/2015
 * Objecto que tem a responsabilidade de manter um registo sobre as modificações 
 * efectuadas ao estado dos objectos da aplicação relevantes para a persistência.
 * Efectua as modificações registadas ou permite reverter (ignorar as mudanças).
 * @author a21210380, a21190325, a21210392
 */
public class UnitOfWork implements IUnityOfWork {
    private static final Logger logger = Logger.getLogger(UnitOfWork.class.getName());

    private final HashMap<Class, Entidade> entidades;
    private final IEntityMap entityMap;
    private final IIDgenerator idGenerator;
    private final IPersistance persistance;
    
    public UnitOfWork(IIDgenerator idGenerator, IEntityMap entityMap, IPersistance persistance) {
        if(idGenerator == null) {
            throw new NullPointerException("O idGenerator não pode ser null!");
        }
        if(entityMap == null) {
            throw new NullPointerException("O entityMap não pode ser null!");
        }
        if(persistance == null) {
            throw new NullPointerException("O persistance não pode ser null!");
        }
        logger.setLevel(Level.SEVERE);
        this.entidades = new HashMap<>();
        this.idGenerator = idGenerator;
        this.entityMap = entityMap;
        this.persistance = persistance;
    }

    /**
     * Inicia a entidade para que seja possivel guardar as operaçoes de persistencia
     * @param <T> extends DataBaseObject
     * @param entidade
     * @return UnitOfWork para fazer chain
     */
    public <T extends DataBaseObject> UnitOfWork iniciarEntidade(Class<T> entidade) {
        if(entidade == null) {
            throw new NullPointerException("A entidade não pode ser null!");
        }
        if(entidades.containsKey(entidade)) {
            logger.logp(Level.WARNING, UnitOfWork.class.getName(), "iniciarEntidade()", "A entidade " + entidade + " já se encontra inicializada!");
            return this;
        }
        entidades.put(entidade, new Entidade());
        return this;
    }
    
    @Override
    public <T extends DataBaseObject> boolean registarNovaEntidade(T entidade) {
        if(entidade == null) {
            throw new NullPointerException("A entidade não pode ser null!");
        }
        if (entidades.containsKey(entidade.getClass())) {
            return entidades.get(entidade.getClass()).registarNovaEntidade(entidade);
        } else {
            logger.logp(Level.WARNING, UnitOfWork.class.getName(), "registarNovaEntidade()", "A entidade " + entidade.getClass() + " não está inicializada!");
            return false;
        }
    }

    @Override
    public <T extends DataBaseObject> void registarEntidadeModificada(T entidade) {
        if(entidade == null) {
            throw new NullPointerException("A entidade não pode ser null!");
        }
        if (entidades.containsKey(entidade.getClass())) {
            entidades.get(entidade.getClass()).registarEntidadeModificada(entidade);
        } else {
            logger.logp(Level.WARNING, UnitOfWork.class.getName(), "registarEntidadeModificada()", "A entidade " + entidade.getClass() + " não está inicializada!");
        }
    }
    
    @Override
    public <T extends DataBaseObject> void registarEntidadeApagada(T entidade) {
        if(entidade == null) {
            throw new NullPointerException("A entidade não pode ser null!");
        }
        if (entidades.containsKey(entidade.getClass())) {
            entidades.get(entidade.getClass()).registarEntidadeApagada(entidade);
        } else {
            logger.logp(Level.WARNING, UnitOfWork.class.getName(), "registarEntidadeApagada()", "A entidade " + entidade.getClass() + " não está inicializada!");
        }
    }

    @Override
    public void commit() {
        idGenerator.guardar();
        if(entidades.isEmpty()) {
            logger.logp(Level.WARNING, UnitOfWork.class.getName(), "commit()", "Não existem entidades!");
            return;
        }
        entidades.forEach((k, v) -> {
            v.commit();
        });
    }

    @Override
    public void rollback() {
        idGenerator.load();
        if(entidades.isEmpty()) {
            logger.logp(Level.WARNING, UnitOfWork.class.getName(), "rollback()", "Não existem entidades!");
            return;
        }
        entidades.forEach((k, v) -> {
            v.rollback();
        });
    }

    private class Entidade<T extends DataBaseObject> {
        private final HashMap<Integer, T> deletedEntitys = new HashMap<>();
        private final HashMap<Integer, T> newEntitys = new HashMap<>();
        private final HashMap<Integer, T> dirtyEntitys = new HashMap<>();
        
        public boolean registarNovaEntidade(T entidade) {
            if(newEntitys.containsKey(entidade.getId())) {
                logger.logp(Level.INFO, UnitOfWork.class.getName(), "Entidade-registarNovaEntidade()", "Já existe! " + entidade);
                return false;
            } else {
                newEntitys.put(entidade.getId(), entidade);
                entityMap.registarNovaEntidade(entidade);
                return true;
            }
        }
        
        public void registarEntidadeModificada(T entidade) {
            if (dirtyEntitys.containsKey(entidade.getId())) {
                logger.logp(Level.INFO, UnitOfWork.class.getName(), "Entidade-registarEntidadeModificada()", "Já existe nas dirtyEntitys! " + entidade);
                return;
            }
            if (newEntitys.containsKey(entidade.getId())) {
                logger.logp(Level.INFO, UnitOfWork.class.getName(), "Entidade-registarEntidadeModificada()", "Já existe nas newEntitys! " + entidade);
                return;
            }
            if(entityMap.procurarEntidadePeloID(entidade.getClass(), entidade.getId()) != null) {
                dirtyEntitys.put(entidade.getId(), entidade);
            } else {
                logger.logp(Level.INFO, UnitOfWork.class.getName(), "Entidade-registarEntidadeModificada()", "A entidade não existe no entity map, por isso não vai ser marcada como modificada! Utilizar o método registarNovaEntidade()! " + entidade);
            }
        }
        
        public void registarEntidadeApagada(T entidade) {
            if(!deletedEntitys.containsKey(entidade.getId())) {
                if(newEntitys.containsKey(entidade.getId())) {
                    entityMap.registarEntidadeApagada(entidade);
                    newEntitys.remove(entidade.getId());
                    logger.logp(Level.INFO, UnitOfWork.class.getName(), "Entidade-registarEntidadeApagada()", "A remover das newEntitys! " + entidade);
                } else {
                    entityMap.registarEntidadeApagada(entidade);
                    deletedEntitys.put(entidade.getId(), entidade);
                }
                if(dirtyEntitys.containsKey(entidade.getId())) {
                    dirtyEntitys.remove(entidade.getId());
                    logger.logp(Level.INFO, UnitOfWork.class.getName(), "Entidade-registarEntidadeApagada()", "A remover das dirtyEntitys! " + entidade);
                }
            } else {
                logger.logp(Level.INFO, UnitOfWork.class.getName(), "Entidade-registarEntidadeApagada()", "Já existe nas deletedEntitys! " + entidade);
            }
        }
        
        public void commit() {
            newEntitys.forEach((k, v) -> {
                if(!persistance.inserirNovaEntidade(v)) {
                    logger.logp(Level.SEVERE, UnitOfWork.class.getName(), "Entidade-commit()", "Erro ao inserir entidade! " + v);
                }
            });
            dirtyEntitys.forEach((k, v) -> {
                if(!persistance.atualizarEntidade(v)) {
                    logger.logp(Level.SEVERE, UnitOfWork.class.getName(), "Entidade-commit()", "Erro ao atualizar entidade! " + v);
                }
            });
            deletedEntitys.forEach((k, v) -> {
                if(!persistance.apagarEntidade(v)) {
                    logger.logp(Level.SEVERE, UnitOfWork.class.getName(), "Entidade-commit()", "Erro ao apagar entidade! " + v);
                }
            });
            newEntitys.clear();
            dirtyEntitys.clear();
            deletedEntitys.clear();
        }
        
        public void rollback() {
            logger.logp(Level.INFO, UnitOfWork.class.getName(), "Entidade-rollback()", "Afazer rollback de newEntitys=" +
                    newEntitys.size() + " dirtyEntitys=" + dirtyEntitys.size() + " deletedEntitys=" + newEntitys.size() );
            
            newEntitys.clear();
            dirtyEntitys.clear();
            deletedEntitys.clear();
        }
    }
}
